//
//  NetworkManager.swift
//  Risac
//
//  Created by remy on 2018/4/2.
//

import Moya
import SwiftyJSON

/// 基础网络管理
public final class NetworkManager {
    public static var global: Options = Options()
    /// 请求结果类型
    public typealias ResultType = Result<JSON, Error>
    /// 缓存操作队列
    private static var ioQueue: DispatchQueue = {
        return DispatchQueue(label: "com.Risac.network.queue")
    }()
    /// 缓存根目录
    private static var cacheRootPath: String = {
        return FileManager.userCachesPath.appendingPathComponent("com.Risac.network.cache")
    }()
    /// 应用版本号区分的缓存目录
    private static var cachePath: String = {
        return cacheRootPath.appendingPathComponent(Bundle.appVersion)
    }()
    
    private init() {}
    
    /// 基础请求
    @discardableResult
    public static func request(target: TargetType,
                               options: ((inout Options) -> Void)? = nil,
                               callbackQueue: DispatchQueue? = .none,
                               completion: ((ResultType) -> Void)? = nil) -> Cancellable? {
        var plugin = NetworkManager.global
        options?(&plugin)
        var cacheKey: String = ""
        if target.method == .get, let cacheTime = plugin.loadPolicy.cacheTime {
            cacheKey = NetworkManager.cacheKey(target)
            if let attr = NetworkManager.cacheAttr(cacheKey), let date = attr[FileAttributeKey.modificationDate] as? NSDate, abs(date.timeIntervalSinceNow) < cacheTime {
                if let data = NetworkManager.cacheData(cacheKey), let json = try? JSON(data: data) {
                    Risac.print("Cache--\(target.method.rawValue)--[\(target.baseURL.appendingPathComponent(target.path))]")
                    completion?(.success(json))
                    if case .cacheElseLoad = plugin.loadPolicy { return nil }
                }
            }
        }
        let provider = MoyaProvider<MultiTarget>(plugins: [plugin])
        return provider.request(MultiTarget(target), callbackQueue: callbackQueue, completion: {
            switch $0 {
            case let .success(response):
                Risac.print("Success--\(target.method.rawValue)--[\(target.baseURL.appendingPathComponent(target.path))]")
                do {
                    _ = try response.filterSuccessfulStatusCodes()
                    let json = try JSON(response.mapJSON())
                    completion?(.success(json))
                    if !cacheKey.isEmpty { NetworkManager.storeData(response.data, cacheKey) }
                } catch {
                    completion?(.failure(error))
                }
            case let .failure(error):
                Risac.print("Failure--\(target.method.rawValue)--[\(target.baseURL.appendingPathComponent(target.path))]")
                Risac.print(error.errorDescription ?? "")
                if case let .underlying(err, _) = error, (err as NSError).code == NSURLErrorCancelled {
                    return
                }
                completion?(.failure(error))
            }
        })
    }
}

extension NetworkManager {
    public struct Options: PluginType {
        /// 请求超时时间
        public var timeout: TimeInterval = 10.0
        /// 加载策略
        public var loadPolicy: LoadPolicy = .loadIgnoringCache
        /// 请求缓存策略
        public var requestCachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData
        /// 请求是否发送cookie
        public var httpShouldHandleCookies: Bool = true
        /// 是否数据预处理
        public var needSuccessProcess: Bool = false
        /// 请求成功数据预处理
        public var responseSuccessProcess: ((JSON, NSErrorPointer) -> Void)?
        
        public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
            var wrap = request
            wrap.timeoutInterval = timeout
            wrap.cachePolicy = requestCachePolicy
            wrap.httpShouldHandleCookies = httpShouldHandleCookies
            return wrap
        }
        
        public func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
            guard needSuccessProcess, let process = responseSuccessProcess else { return result }
            guard case let .success(response) = result, let json = try? JSON(response.mapJSON()) else { return result }
            var error: NSError?
            process(json, &error)
            if let error = error {
                return Result<Response, MoyaError>.failure(MoyaError.underlying(error, response))
            }
            return result
        }
        
        fileprivate init() {}
    }
    
    /// 加载策略
    public enum LoadPolicy {
        /**
         # 缓存未过期: 使用缓存数据,并发送网络请求更新数据和缓存
         # 缓存过期: 发送网络请求更新数据和缓存
         */
        case cacheAndLoad(TimeInterval)
        /**
         # 缓存未过期: 使用缓存数据,不发送网络请求
         # 缓存过期: 发送网络请求更新数据和缓存
         */
        case cacheElseLoad(TimeInterval)
        /// 忽略缓存,总是发送网络请求
        case loadIgnoringCache
        
        var cacheTime: TimeInterval? {
            switch self {
            case let .cacheAndLoad(time), let .cacheElseLoad(time):
                return time
            default:
                return nil
            }
        }
    }
}

extension NetworkManager {
    fileprivate static func cacheKey(_ target: TargetType) -> String {
        // URLRequest根据url字符串生成hashValue
//        let url = target.path.isEmpty ? target.baseURL : target.baseURL.appendingPathComponent(target.path)
//        return "\(url.absoluteString.hashValue)"
        let str = "\(target)"
        return "\(str.hashValue)"
    }
    
    private static func cacheFilePath(_ key: String) -> String {
        return cachePath.appendingPathComponent(key)
    }
    
    /// 读取缓存文件信息
    private static func cacheAttr(_ key: String) -> [FileAttributeKey: Any]? {
        if key.isEmpty { return nil }
        let filePath = cacheFilePath(key)
        return try? FileManager.default.attributesOfItem(atPath: filePath)
    }
    
    /// 读取缓存
    private static func cacheData(_ key: String) -> Data? {
        if key.isEmpty { return nil }
        let filePath = cacheFilePath(key)
        return FileManager.default.contents(atPath: filePath)
    }
    
    /// 写入缓存
    private static func storeData(_ data: Data, _ key: String) {
        if key.isEmpty { return }
        ioQueue.async {
            var isDir: ObjCBool = true
            if !FileManager.default.fileExists(atPath: cachePath, isDirectory: &isDir) {
                // 删除旧版本缓存
                try? FileManager.default.removeItem(atPath: cacheRootPath)
                try? FileManager.default.createDirectory(atPath: cachePath, withIntermediateDirectories: true)
            }
            let filePath = cacheFilePath(key)
            FileManager.default.createFile(atPath: filePath, contents: data)
        }
    }
    
    /// 清除缓存
    public static func clearCache(completion: (() -> Void)? = nil) {
        ioQueue.async {
            try? FileManager.default.removeItem(atPath: cacheRootPath)
            if let completion = completion {
                QueueTask.main {
                    completion()
                }
            }
        }
    }
    
    /// 缓存大小,单位字节
    public static func cacheSize(completion: ((UInt64) -> Void)? = nil) {
        ioQueue.async {
            var size: UInt64 = 0
            var isDir: ObjCBool = true
            if FileManager.default.fileExists(atPath: cacheRootPath, isDirectory: &isDir) {
                if let fileEnumerator = FileManager.default.enumerator(atPath: cacheRootPath) {
                    fileEnumerator.forEach {
                        if let fileName = $0 as? String {
                            let filePath = cacheFilePath(fileName)
                            if let attr = try? FileManager.default.attributesOfItem(atPath: filePath) {
                                size += attr[FileAttributeKey.size] as! UInt64
                            }
                        }
                    }
                }
            }
            if let completion = completion {
                QueueTask.main {
                    completion(size)
                }
            }
        }
    }
}
